package site.luojie.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.rpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 简易版性能监控
 *
 * @author jie.luo
 * @since 2020/8/31
 */
@Activate(group = CommonConstants.CONSUMER)
public class TpMonitorFilter implements Filter, Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(TpMonitorFilter.class);
    /**
     * 一分钟的毫米数
     */
    private static final long ONE_MI_MILLIS = 60 * 1000;
    private static final double TOP_50 = 0.50;
    private static final double TOP_95 = 0.95;
    private static final double TOP_99 = 0.99;

    private Map<String, List<ResponseTime>> TOP = new ConcurrentHashMap<>();

    public TpMonitorFilter() {
        // 没隔5秒打印 耗时情况
        Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(this, 1, 5, TimeUnit.SECONDS);
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        final String methodName = invocation.getMethodName();

        long startTime = System.currentTimeMillis();
        try {
            return invoker.invoke(invocation);
        } finally {
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;

            if (TOP.containsKey(methodName)) {
                List<ResponseTime> longs = TOP.get(methodName);
                longs.add(new ResponseTime(endTime, responseTime));
            } else {
                List<ResponseTime> longs = new ArrayList<>();
                longs.add(new ResponseTime(endTime, responseTime));
                TOP.put(methodName, longs);
            }
        }
    }

    @Override
    public void run() {
        for (Map.Entry<String, List<ResponseTime>> entry : TOP.entrySet()) {
            // 请求方法
            String method = entry.getKey();

            // 缓存的所有响应数据
            List<ResponseTime> requestCount = entry.getValue();

            // 当前时间
            final long currentTime = System.currentTimeMillis();

            // 过滤最近一分钟的请求信息
            List<ResponseTime> filterTime = requestCount.stream().filter(responseTime ->
                    responseTime.getCreateTime() > currentTime - ONE_MI_MILLIS
            ).collect(Collectors.toList());

            // 重置缓存响应信息
            TOP.put(method, filterTime);

            if (CollectionUtils.isEmpty(filterTime)) {
                LOGGER.info("最近1分钟【{}】方法总的请求数:{} ", method, 0);
                continue;
            }

            // 总的请求数
            final int totalRequest = filterTime.size();

            List<ResponseTime> collect = filterTime.stream().sorted(Comparator.comparing(ResponseTime::getResponseTime)).collect(Collectors.toList());

            int t50 = (int) (TOP_50 * totalRequest);
            int t95 = (int) (TOP_95 * totalRequest);
            int t99 = (int) (TOP_99 * totalRequest);

            LOGGER.info("最近1分钟【{}】方法总的请求数:{} , TP50: {} ms,TP95: {} ms,TP99: {} ms",
                    method, totalRequest, collect.get(t50).getResponseTime(), collect.get(t95).getResponseTime(), collect.get(t99).getResponseTime());
        }
    }


    private class ResponseTime {
        private Long createTime;
        private Long responseTime;

        public ResponseTime(Long createTime, Long responseTime) {
            this.createTime = createTime;
            this.responseTime = responseTime;
        }

        public Long getCreateTime() {
            return createTime;
        }

        public void setCreateTime(Long createTime) {
            this.createTime = createTime;
        }

        public Long getResponseTime() {
            return responseTime;
        }

        public void setResponseTime(Long responseTime) {
            this.responseTime = responseTime;
        }
    }
}
